package cjq.demo.nio.ch2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

/**
 * nio服务端的主要创建过程
 1.打开ServerSocketChannel,用于监听客户端连接。
 2.绑定监听端口，设置连接为非阻塞模式。
 3.创建Reactor线程，创建多路复用器并启动线程。
 4.将ServerSocketChannel注册到Reactor线程的多路复用器Selector上，监听ACCEPT事件。
 5.多路复用器在线程run方法的无限循环体内轮询准备就绪的key。
 6.多路复用器监听新客户端接入，处理新的接入请求，完成tcp三次握手，建立物理链路。
 7.设置客户端链路为非阻塞模式。
 8.将新接入的客户端连接注册到Reactor线程的多路复用器上，监听读操作，用来读取客户端发送的网络消息。
 9.异步读取客户端请求消息到缓冲区。
 10.对ByteBuffer进行编解码，如果有半包消息指针reset，继续读取后续的报文，将解码成功的消息封装成Task，投递到业务线程池中，进行业务逻辑编排。
 11.将pojo对象encode成ByteBuffer，调用SocketChannel的异步write接口，将消息异步发送给客户端。
 * Created by cjq on 2016/4/15.
 */
public class MultiplexerTimeServer implements Runnable{
    private Selector selector;

    private ServerSocketChannel servChannel;

    private volatile boolean stop;

    /**
     *
     * @param port
     */
    public MultiplexerTimeServer(int port){
        try{
            //创建多路复用器Selector、ServerSocketChannel
            selector = Selector.open();
            servChannel = ServerSocketChannel.open();
            //对Channel和tcp参数进行配置，将ServerSocketChannel设置异步非阻塞模式
            servChannel.configureBlocking(false);
            //backlog设置为1024
            servChannel.socket().bind(new InetSocketAddress(port),1024);
            //将ServerSocketChannel注册到Selector,监听SelectionKey.OP_ACCEPT操作位
            servChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("The time server is start in port : "+ port);
        }catch (Exception e){
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop(){
        this.stop = true;
    }

    public void run(){
        while(!stop){
            try{
                //休眠时间为1s
                //无论是否有读写事件发生， selector每隔1s都被唤醒一次
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey key = null;
                while(it.hasNext()){
                    key = it.next();
                    try{
                        handleInput(key);
                    }catch (Exception e){
                        if(key!=null){
                            key.cancel();
                            if(key.channel()!=null){
                                key.channel().close();
                            }
                        }
                    }
                }
            }catch (Throwable t){
                t.printStackTrace();
            }
        }

        //多路复用器关闭后，
        // 所有注册在上面的channel和Pipe等资源都会被自动去注册并关闭，
        // 所以不需要重复释放资源
        if(selector!=null){
            try{
                selector.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

    //处理新接入的客户端请求消息
    private void handleInput(SelectionKey key) throws IOException{
        if(key.isValid()){
            if(key.isAcceptable()){
                //通过ServerSocketChannel的accept接收客户端的连接请求并创建SocketChannel实例
                //此时相当完成了tcp的三次握手，TCP物理链路正式建立
                ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                sc.register(selector,SelectionKey.OP_READ);
            }
            //读取客户端的请求消息
            if(key.isReadable()){
                SocketChannel sc = (SocketChannel)key.channel();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(readBuffer);
                if(readBytes>0){
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes,"UTF-8");
                    System.out.println("The time server receive order:"+body);
                    String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
                            new Date(System.currentTimeMillis()).toString():
                            "BAD ORDER";
                    doWrite(sc,currentTime);
                }else if(readBytes<0){
                //返回值为－1：链路已经关闭，需要关闭SocketChannel，释放资源
                    key.cancel();
                    sc.close();
                }
            }
        }
    }

    //将应答消息异步发送给客户端
    private void doWrite(SocketChannel channel, String response)  throws  IOException{
        if(response!=null && response.trim().length()>0){
            byte[] bytes = response.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            writeBuffer.put(bytes);
            writeBuffer.flip();
            channel.write(writeBuffer);
        }
    }


}
